package cn.gyn.gynrpc.core.consumer;


import cn.gyn.gynrpc.core.annotation.GynConsumer;
import cn.gyn.gynrpc.core.api.*;
import cn.gyn.gynrpc.core.meta.InstanceMeta;
import cn.gyn.gynrpc.core.meta.ServiceMeta;
import cn.gyn.gynrpc.core.util.MethodUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 消费者启动类
 */
@Data
@Slf4j
public class ConsumerBootstrap implements ApplicationContextAware, EnvironmentAware {
    ApplicationContext applicationContext;

    //Environment 接口提供了方法来访问属性和配置信息的相关方法，包括 getProperty,获取yml等配置文件里的信息
    Environment environment;

    //consumer端的桩子叫stub,provider端的桩子叫skeleton
    private Map<String, Object> stub = new HashMap<>();

    /*
    @Value("${app.id}")
    private String app;

    @Value("${app.namespace}")
    private String namespace;

    @Value("${app.env}")
    private String env;

    @Value("${app.retries}")
    private int retries;

    @Value("${app.timeout}")
    private int timeout;*/

    /**
     * 调用此方法时Spring已经完全初始化成功了
     * 这里拿到上下文给注解了@GynConsumer的field进行赋值
     * 20240523重写过一版
     */
    public void start() {
        //思考一下这里为啥要把router的类型固化为serviceMeta，原来是泛型只有Router
        RegistryCenter rc = applicationContext.getBean(RegistryCenter.class);
        log.info("ConsumerBootstrap123 rc===> {}", rc);
        RpcContext context = applicationContext.getBean(RpcContext.class);

        String[] names = applicationContext.getBeanDefinitionNames();
        for (String name : names) {
            //获取所有的bean
            Object bean = applicationContext.getBean(name);
            log.info("ConsumerBootstrap123 name===> {},names===> {}", name, names);
            log.info("ConsumerBootstrap123 bean ===> {}", bean);
            List<Field> fields = MethodUtils.findAnnotatedField(bean.getClass(), GynConsumer.class);

            log.info("ConsumerBootstrap123 fields ===> {}", fields);
            //foreach每一个生成代理
            fields.stream().forEach(f -> {
                log.info("ConsumerBootstrap123 f ===> {}", f);

                //输入f.getType().var自动生成结果定义：获取field类型
                Class<?> service = f.getType();
                //获取其全限定名称
                String serviceName = service.getCanonicalName();
                //看下桩子里是不是已经存在。这里可以用 computeIfAbsent 方法更新查看是否已存在的键的值,
                //map.computeIfAbsent("key", k -> 20); 如果map中已存在key，返回key对应的原本的值，如果不存在，给他赋一个20的值
                log.info("ConsumerBootstrap123 f.getName ===> {}", f.getName());
                try {
                    log.info("ConsumerBootstrap123 ===> consumer stub: " + stub);
                    Object consumer = stub.get(serviceName);
                    if (consumer == null) {
                        log.info("ConsumerBootstrap123 ===> consumer is null");
                        //预习内容：创建代理对象的四种方法:1、动态代理;2、enhancer;3、spring proxy;4、bytebuddy
                        //代理对象的出现使得对原始对象的访问能够受到控制，并且可以在不改变原始对象的情况下添加额外的逻辑。
                        /**
                         * 在Java中，创建代理对象主要用于实现面向切面编程（AOP）和代理模式，以实现以下功能：
                         * 安全控制：代理对象可以在调用原始对象的方法之前进行安全性检查，以确保用户有合适的权限进行操作。
                         * 日志记录：代理对象可以记录方法的调用时间、参数等信息，用于性能监控和日志记录。
                         * 事务管理：代理对象可以在事务开始和结束时执行相关的逻辑，从而实现事务的管理和控制。
                         * 懒加载：在需要时才实例化原始对象，延迟加载资源。
                         * 远程调用：通过代理对象可以在调用前后进行网络通信，从而实现本地代码调用远程服务的方法。
                         * 统一的编程接口：代理对象提供一个统一的编程接口，可以屏蔽各种具体细节实现，提高客户端代码的简洁性和可读性。
                         * 通过代理对象，我们可以对原始对象的访问进行控制和管理，允许在调用原始对象的方法前后执行特定的逻辑。
                         * 代理对象可以让我们在不改变原始对象的情况下，通过添加额外的逻辑来实现一些公共的或者横切关注点的功能。
                         * 因此，代理对象在Java中的应用可以帮助我们实现更加灵活、模块化、清晰的代码结构。
                         */
                        //consumer = createConsumer(service, context, List.of(providers));
                        //改为用rc获取providers
                        consumer = createFromRc(service, context, rc);
                        log.info("ConsumerBootstrap123 ===> createFromRc {}", consumer);
                        stub.put(serviceName, consumer);
                    }
                    //使用了setAccessible(true)方法来取消对私有字段privateField的访问限制，然后可以通过反射机制来获取它的值。在实际开发中，setAccessible方法可以用于解决部分场景下的特定问题，但由于潜在的安全风险，应该谨慎使用。
                    f.setAccessible(true);
                    f.set(bean, consumer);
                } catch (Exception ex) {
                    log.warn(" ==> Field[{}.{}] create consumer failed.", serviceName, f.getName());
                    log.error("Ignore and print it as: ", ex);
                }
            });
        }
        log.info("ConsumerBootstrap123 ===> consumer stub: " + stub);
    }


    /**
     * 通过注册中心获取providers，并创建一个订阅，再创建一个consumer的代理进行网络调用
     * @param service
     * @param context
     * @param rc
     * @return
     */
    private Object createFromRc(Class<?> service, RpcContext context, RegistryCenter rc) {
        ServiceMeta serviceMeta = ServiceMeta.builder()
                .app(context.param("app.id"))
                .namespace(context.param("app.namespace"))
                .env(context.param("app.env"))
                .name(service.getCanonicalName())
                .build();
        List<InstanceMeta> providers = rc.fetchAll(serviceMeta);
        log.info(" ====> map to providers:");
        providers.forEach(System.out::println);

        //订阅变化，重新赋值最新的providers
        /*//lambda表达式替换
        rc.subscribe(serviceName, new ChangedListener() {
            @Override
            public void fire(Events events) {
                providers.clear();
                providers.addAll(events.getData());
            }
        });*/
        rc.subscribe(serviceMeta, events -> {
            providers.clear();
            providers.addAll(events.getData());
        });
        return createConsumer(service, context, providers);
    }

    /**
     * 00:31:55
     *
     * @param service
     * @param context
     * @param providers
     * @return
     */
    private Object createConsumer(Class<?> service, RpcContext context, List<InstanceMeta> providers) {
        //创建代理
        return Proxy.newProxyInstance(
                //类加载器
                service.getClassLoader(),
                //创建了一个包含单个接口的 Class 类型数组。
                new Class[]{service},
                //调用处理器（一个拦截器）
                new GynInvocationHandler(service, context, providers)
        );
    }
}
